#include <string.h>

#include "lib.h"
#include "stream.h"
#include "hashtable.h"
using namespace Library;

#include "packagemanagement.h"
#include "pkgmgr.h"
#include "pkg.h"
#include "pkgmfest.h"
using namespace PackageManagement;

#include "apppath.h"
#include "pathname.h"
#include "zipfile.h"
#include "dir.h"

#define PACKAGENAME_LOBBY		"Lobby"
#define PACKAGENAME_CASHIER		"Cashier"


PackageMgr::PackageMgr(pCApp app)
{
	m_app		= app;
	m_packages	= new HashTable<pPackage>(50);	
	m_files		= new HashTable<int32*>(50);
}

PackageMgr::~PackageMgr()
{
	HashTableIterator<pPackage> iterator(m_packages);

	for (iterator.begin(); iterator.key(); iterator++)
		delete iterator.value();

	delete m_packages;
	m_packages = 0;

	HashTableIterator<int32*> iteratefiles(m_files);

	for (iteratefiles.begin(); iteratefiles.key(); iteratefiles++)
		delete iteratefiles.value();

	delete m_files;
	m_files = 0;
}

bool8
PackageMgr::loadList(const String& filename)
{
	m_filename = filename;

	InstalledPackagesManifest ipm(filename);
	ipm.load(this);
	
	//
	// The Lobby package *MUST* exist.  Everything else depends upon it.
	//
	pPackage lobby = findLobby();
	if (!lobby)
	{
		// try "installed/Lobby"
		//
		PackageManifest pm;
		Package* pkg = new Package();
		pkg->setName("Lobby");

		if (!pm.load(pkg))
		{
			// nothing...force a lobby update
			//
			pkg->setName(PACKAGENAME_LOBBY);
			pkg->setLocalVersion(0x00000000L);	// this forces the update
			pkg->setInstalled(true);
			pkg->setDLLName("lobby");
		}

		addPackage(pkg);
	}
	else 
	{
		//if we're running the lobby has to be installed
		lobby->setInstalled(true);
	}

	//
	// The Bank package *MUST* exist.
	//
	if (!findCashier())
	{
		// try "installed/Cashier"
		//
		PackageManifest pm;
		Package* pkg = new Package();
		pkg->setName("Cashier");

		if (!pm.load(pkg))
		{
			// nothing...force a bank update
			//
			pkg->setName(PACKAGENAME_CASHIER);
			pkg->setLocalVersion(0x00000000L);	// this forces the update
			pkg->setInstalled(false);
		}

		addPackage(pkg);
	}

	return true;
}

bool8
PackageMgr::saveList()
{
	InstalledPackagesManifest ipm(m_filename);

	return ipm.save(this);
}

bool8
PackageMgr::installRSync(pPackage package, int32* errCode, bool8 showMessage)
{
	if (package->isLobby())
		return installRSyncLobby(package, showMessage);
	else
		return installRSyncGame(package, errCode);
}

bool8
PackageMgr::install(pPackage package, const PathName & zipFilename)
{
	if (package->isLobby())
		return installLobby(package, zipFilename);
	else
		return installGame(package, zipFilename);
}


bool8
PackageMgr::installRSyncGame(pPackage package, int32* errCode)
{
	bool8 result;
	PathName dest;

	if(!(dest.set(AppPath::getPatchDir()) && 
		dest.concat(package->getName())))
		return false;

	uninstall(package);

	package->setInstalled(false);

	result = Directory::moveContents(dest, AppPath::getExeDir(), errCode);
	if (result)
	{
		PackageManifest pm;

		//the package comming from the server will not have the unzip size filled in
		//save it off and 
		uint32 unzipsize = package->getUnZipSize();
		uint32 servver = package->getServerVersion();

		result = pm.load(package);
		if (result)
		{
			package->setUnZipSize(unzipsize);
			package->setInstalled(true);
			if (servver)
				package->setLocalVersion(servver);

			pm.loadFiles(package, this);

			saveList();

			installGameHelp(package);

			Directory::removeAll(dest);
		}
	}
	
	return result;
}

bool8
PackageMgr::installGame(pPackage package, const PathName & zipFilename)
{
	bool8 result;
	ZipFile zf;
	PathName dest;
	uint32 ext;

	if (!(dest.set(AppPath::getTempDir()) &&
		dest.concat(zipFilename.getEnd())))
		return false;

	// remove extension
	//
	ext = dest.getEnd().find(".");
	if (ext != String::NPOS)
		dest.shrink(dest.getLen() - ext);

	if (!Directory::make(dest))
		return false;

	if (!zf.open(zipFilename))
	{
		Directory::remove(dest);
		return false;
	}

	result = unzipPackage(zf, dest);

	zf.close();

	if (result)
	{
		uninstall(package);

		result = Directory::moveContents(dest, AppPath::getExeDir());
		if (result)
		{
			PackageManifest pm;

			//the package comming from the server will not have the unzip size filled in
			//save it off and 
			uint32 unzipsize = package->getUnZipSize();
			uint32 servver = package->getServerVersion();

			result = pm.load(package);
			if (result)
			{
				package->setUnZipSize(unzipsize);
				package->setInstalled(true);
				package->setLocalVersion(servver);

				saveList();

				installGameHelp(package);
			}
		}
	}

	Directory::removeAll(dest);

	return result;
}

bool8
PackageMgr::unzipPackage(ZipFile& ZF, PathName& Dest)
{
	ZipEntry* ze;
	pCStream out = (pCStream)0;
	UnzipStream* uzs = (UnzipStream*)0;
	int32 amt, origDestLen;
	uint8 buf[4096];

	// caller must clean up Dest directory upon failure
	//
	origDestLen = Dest.getLen();

	ze = ZF.getZipEntries();
	while (ze)
	{
		if (ze->isFilenameValid() && !ze->isEncrypted())
		{
			if (!Dest.concat(ze->filename))
				goto abort;

			// change unix separators (stored in zipfile)
			//
			Dest.replace('/', Directory::sep);

			if (ze->isDirectory())
			{
				if (!Directory::makeAll(Dest))
					goto abort;
			}
			else
			{
				if (!Directory::makeAll(Dest, Directory::EndIsFilename))
					goto abort;

				out = newCStream(Dest, streamO_WRONLY);
				if (!out)
					goto abort;

				uzs = ze->getUnzipStream(&ZF);
				if (uzs)
				{
					while ((amt = uzs->read(buf, 4096)) >= 0)
					{
						if (amt > 0 && amt != CStreamWrite(out, buf, amt))
						{
							amt = -2;
							break;
						}
					}

					if (amt < -1)
						goto abort;

					deleteCStream(out);
					delete uzs;
					out = (pCStream)0;
					uzs = (UnzipStream*)0;
				}
				else
					goto abort;
			}

			Dest.shrink(Dest.getLen() - origDestLen);
		}
		else
			goto abort;

		ze = ze->getNext();
	}

	return true;

abort:
	delete uzs;
	deleteCStream(out);
	Dest.shrink(Dest.getLen() - origDestLen);

	return false;
}

void
PackageMgr::uninstall(pPackage package)
{
	PackageManifest pm;

	pm.uninstall(package, this);

	package->setInstalled(false);
}

pPackage
PackageMgr::find(const String& packageName)
{
	pPackage	package;

	if (m_packages->get(packageName, &package) == true)
		return package;

	return 0;
}

pPackage
PackageMgr::findLobby()
{
	pPackage	package;

	if (m_packages->get(PACKAGENAME_LOBBY, &package) == true)
		return package;

	return 0;
}

Package*
PackageMgr::findCashier()
{
	pPackage	package;

	if (m_packages->get(PACKAGENAME_CASHIER, &package) == true)
		return package;

	return 0;
}

bool8
PackageMgr::addPackage(pPackage package)
{
	if (!m_packages || !package)
		return false;

	return m_packages->add(package->getName(), package);
}

bool8
PackageMgr::addFile(const String& file)
{
	int32 *count = 0;

	if (m_files->get(file, &count) == true && count)
	{
		(*count)++;
		return true;
	}

	count = new int32;
	if (count)
		*count = 1;

	return m_files->add(file, count);
}

bool8
PackageMgr::removeFile(const String& file)
{
	int32 *count = 0;

	if (m_files->get(file, &count) == true && count)
	{
		(*count)--;

		if (*count <= 0)
		{
			*count = 0;

			//ignore the result a file might not be on the hard-drive
			CStreamDelFile(file);
			return true;
		}

		return true;
	}

	//ignore the result a file might not be on the hard-drive
	CStreamDelFile(file);
	return true;
}

bool8
PackageMgr::removePackage(pPackage package)
{
	if (!m_packages || !package)
		return false;

	return m_packages->remove(package->getName());
}

uint32
PackageMgr::packageCount()
{
	if (!m_packages)
		return 0;

	return m_packages->size();
}

HashTableIterator<pPackage> *
PackageMgr::iterator()
{
	return new HashTableIterator<pPackage>(m_packages);
}
